{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 126,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import os\n",
    "import yaml\n",
    "import random\n",
    "from aip import AipSpeech\n",
    "root_dir = \"/xiaobai/\"\n",
    "sys.path.append(root_dir)\n",
    "import snowboydecoder\n",
    "class XiaoBai:\n",
    "    #初始化函数，设置\n",
    "    def __init__(self,keyword_model):\n",
    "        self.detector = snowboydecoder.HotwordDetector(keyword_model, sensitivity=0.5)\n",
    "        self.skills = []\n",
    "        self.greetings = [\"嗯哼.mp3\",\"我在.mp3\",\"请说.mp3\"]\n",
    "        with open(root_dir+\"config.yaml\") as f:\n",
    "            config = yaml.load(f)['baidu_yuyin']\n",
    "            self.client = AipSpeech(config['app_id'], config['api_key'], config['secret_key'])\n",
    "    #检测到关键字后的操作\n",
    "    def callback(self):\n",
    "            self.detector.terminate()\n",
    "            n = random.randint(0,len(self.greetings)-1)\n",
    "            notify_sound = root_dir+'resources/greetings/'+self.greetings[n]\n",
    "            os.system(\"mpg123 \"+notify_sound)            \n",
    "            res = self.listen_and_recognize()\n",
    "            if res == \"\":\n",
    "                print('你：\"\"(你什么也没说)')\n",
    "                self.speak(\"\")\n",
    "            else:\n",
    "                print(\"你：\"+res)\n",
    "                handled = False\n",
    "                for skill in self.skills:\n",
    "                    if skill.handle(res,callback=self.speak):\n",
    "                        handled = True\n",
    "                        break\n",
    "                if not handled:\n",
    "                    self.speak(\"小白暂时不会处理呢\")\n",
    "            self.detector.start(detected_callback=self.callback,sleep_time=0.03)\n",
    "    #添加技能\n",
    "    def add_skill(self,skill):\n",
    "        if skill.type == \"skill\":\n",
    "            self.skills.append(skill)\n",
    "    def listen_for_keyword(self):\n",
    "        try:\n",
    "            print('Listening...')\n",
    "            self.detector.start(detected_callback=self.callback,sleep_time=0.03)\n",
    "        except KeyboardInterrupt:\n",
    "            print('stop')\n",
    "        finally:\n",
    "            self.detector.terminate()            \n",
    "    #录音和识别函数,调用arecord录音\n",
    "    def listen_and_recognize(self,length = 4):\n",
    "        os.system(\"arecord -d %d -r 16000 -c 1 -t wav -f S16_LE record.wav\" % (length,) )    \n",
    "        with open(\"./record.wav\", 'rb') as fp:\n",
    "            res = self.client.asr(fp.read(), 'wav', 16000, { 'dev_pid': 1536,})\n",
    "            if res['err_no']==0:\n",
    "                return res[\"result\"][0]\n",
    "            else:\n",
    "                #print(res)\n",
    "                return \"\"\n",
    "    #调用百度语音合成API进行回复\n",
    "    def speak(self,text = '你好百度',lang = 'zh',type = 1 , vol = 5, spd = 5 , pit = 5):\n",
    "        if text == \"\":\n",
    "            print('小白：......')\n",
    "            return\n",
    "        result  = self.client.synthesis(text, lang, type, {'vol': vol,'spd':spd,'pit':pit})\n",
    "        # 识别正确返回语音二进制 错误则返回dict\n",
    "        if not isinstance(result, dict):\n",
    "            with open('speak.mp3', 'wb') as f:\n",
    "                f.write(result)\n",
    "            print('小白：'+text)\n",
    "            os.system('mpg123 speak.mp3')\n",
    "        else:\n",
    "            print('emmmm，小白出错了呢')\n",
    "            print(result)\n",
    "            \n",
    "import abc #利用abc模块实现抽象类\n",
    "#编写扩展技能的基本格式，has_intent函数检测是否有需要该技能处理的意图，action函数执行对应的处理\n",
    "class BaseSkill(metaclass=abc.ABCMeta):\n",
    "    type='skill'\n",
    "    #参数说明 \n",
    "    #    text：语音识别的到的文本\n",
    "    #    callback：反馈文本的处理函数，默认直接打印，也可以传入语音合成函数进行语音回复\n",
    "    #定义抽象方法，检测是否有需要该技能处理的意图\n",
    "    @abc.abstractmethod \n",
    "    def has_intent(self,text=\"\"):\n",
    "        pass\n",
    "    #定义抽象方法，根据意图处理处理信息\n",
    "    @abc.abstractmethod\n",
    "    def action(self,text=\"\"):\n",
    "        pass\n",
    "    #处理函数，根据意图处理处理信息，返回是否继续检测意图\n",
    "    def handle(self,text=\"\",callback=print):\n",
    "        if self.has_intent(text=text):\n",
    "            self.action(text=text,callback=callback)\n",
    "            return True\n",
    "        else:\n",
    "            return False   \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 125,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "找到，许嵩 - 宇宙之大,为您播放\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import argparse\n",
    "import random\n",
    "import subprocess\n",
    "\n",
    "import re\n",
    "class MusicSkill(BaseSkill): \n",
    "    #递归找出指定文件夹下的所有mp3文件\n",
    "    def getfiles(dir): \n",
    "        lists = []\n",
    "        for item in os.listdir(dir): \n",
    "            path = os.path.join(dir, item) \n",
    "            if os.path.isdir(path): \n",
    "                lists.extend(getfiles(path))\n",
    "            elif os.path.splitext(path)[1]==\".mp3\" :\n",
    "                lists.append(path)\n",
    "        return lists\n",
    "    #根据关键字查找对应的mp3文件，如果有多个匹配项，随机返回一个\n",
    "    def find_music(self,keyword = \"\"):\n",
    "        result=[]\n",
    "        for item in self.lists:\n",
    "            if item.find(keyword) >= 0:\n",
    "                result.append(item)\n",
    "        if len(result) > 0:\n",
    "            n = random.randint(0,len(result)-1)\n",
    "            music = result[n]\n",
    "            return music\n",
    "        else:\n",
    "            return None\n",
    "    #调用系统命令播放音乐\n",
    "    def play_music(path):\n",
    "        os.system('mpg123 \"'+ path+'\"')    \n",
    "    def __init__(self):\n",
    "        path = \"/xiaobai/resources/music\"\n",
    "        self.lists = getfiles(path)\n",
    "    #继承BaseSkill类，必须定义has_intent和action方法\n",
    "    def has_intent(self,text=\"\"):\n",
    "        keywords = [\"我想听\",\"播放\"] #如果有说 我想听、播放 一类的词就认为有播放音乐的意图，再由action函数判断应该播放哪一首歌曲\n",
    "        for i in keywords:\n",
    "            if text.find(i)>=0:\n",
    "                return True\n",
    "        return False\n",
    "    def action(self,text=\"\",callback=print):\n",
    "        m = re.search('(我想听|播放)(.+?)(的歌$|$)', text)\n",
    "        if m is not None:\n",
    "            #search = m.groups()[1].replace('的',' ')\n",
    "            search = m.groups()[1]\n",
    "            result = self.find_music(search)\n",
    "            if result is not None:\n",
    "                callback(\"找到，\"+os.path.basename(result).replace('.mp3','')+\",为您播放\")\n",
    "                play_music(result)\n",
    "            else:\n",
    "                callback(\"未找到\",search)\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    s = MusicSkill()\n",
    "    s.handle(\"我想听许嵩的歌\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Listening...\n",
      "stop\n"
     ]
    }
   ],
   "source": [
    "keyword_model = root_dir+'resources/小白.pmdl'\n",
    "xiaobai = XiaoBai(keyword_model=keyword_model)\n",
    "xiaobai.add_skill(MusicSkill())\n",
    "xiaobai.listen_for_keyword()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "小白：我~在~\n"
     ]
    }
   ],
   "source": [
    "keyword_model = root_dir+'resources/小白.pmdl'\n",
    "xiaobai = XiaoBai(keyword_model=keyword_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [],
   "source": [
    "xiaobai.speak(\"我在\",spd=4,pit=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [],
   "source": [
    "xiaobai.speak(\"请说\",spd=5,pit=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [],
   "source": [
    "xiaobai.speak(\"峎哼\",spd=7,pit=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "小白：呀,出错了呢\n"
     ]
    }
   ],
   "source": [
    "xiaobai.speak(\"呀,出错了呢\",spd=5,pit=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 175,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请问尊名～\n",
      "北京:周日,晴 西南风3-4级,最低气温13度，最高气温26度\n",
      "家果家禽梁国名人孔坦拜访姓杨的朋友。杨家一个9岁的孩子端出杨梅招待客人。孔坦开玩笑说：“这是你家的家果吧。”孩子应声说：“没听说孔雀就是你家的家禽呢！”\n"
     ]
    }
   ],
   "source": [
    "import requests\n",
    "import json\n",
    "import yaml\n",
    "\n",
    "class TalkSkill(BaseSkill):\n",
    "    def __init__(self):\n",
    "        root_dir = \"/xiaobai/\"\n",
    "        with open(root_dir+\"config.yaml\") as f:\n",
    "            config = yaml.load(f)['tuling']\n",
    "            self.key = config['key']\n",
    "            self.url = 'http://www.tuling123.com/openapi/api'\n",
    "    #继承BaseSkill类，必须定义has_intent和action方法\n",
    "    def has_intent(self,text=\"\"):\n",
    "        if text!= \"\":\n",
    "            return True\n",
    "        return False\n",
    "    def action(self,text=\"\",callback=print):\n",
    "        try:\n",
    "            req = {'key':self.key,'info':text}\n",
    "            res = requests.post(url = self.url, data = req)\n",
    "            #print(res.text)\n",
    "            jd = json.loads(res.text)\n",
    "            callback(jd['text'])\n",
    "        except:\n",
    "            callback(\"出错了呢，可能网络不太好\")\n",
    "if __name__ == '__main__':\n",
    "    s = TalkSkill()\n",
    "    s.handle(\"你好呀\")\n",
    "    s.handle(\"北京明天天气怎么样\")\n",
    "    s.handle(\"讲个冷笑话\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 176,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Listening...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:snowboy:Keyword 1 detected at time: 2019-05-11 07:37:41\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你：个冷笑话\n",
      "小白：忘了切两半别佳到一家常去的餐馆进餐。煎肉片端来后，别佳翻来覆去只 有一块，便问：“我以前来这里吃煎肉片，你们都给两块，今天怎 么只有一块？” “啊，对不起，这是厨师粗心大意，忘了把肉切成两片了。”\n",
      "stop\n"
     ]
    }
   ],
   "source": [
    "keyword_model = root_dir+'resources/小白.pmdl'\n",
    "xiaobai = XiaoBai(keyword_model=keyword_model)\n",
    "xiaobai.add_skill(TalkSkill())\n",
    "xiaobai.listen_for_keyword()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
